﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

using IQToolkit;

namespace LINQToolKit.Query
{
    /// <summary>
    /// represents the base class for translating the expression to query texts
    /// </summary>
    public abstract class TranslatorBase : ExpressionVisitor, ITranslator
    {
        private IList<string> _queries;
        private string _queryString;

        public TranslatorBase()
        {
            this._queries = new List<string>();
            this._queryString = string.Empty;
        }

        public static IList<string> Union(
            IEnumerable<string> listx, IEnumerable<string> listy, IEqualityComparer<string> comparer)
        {
            IList<string> list = new List<string>();

            foreach (var x in listx)
            {
                foreach (var y in listy)
                {
                    if (comparer.Equals(x, y))
                    {
                        list.Add(x);
                    }
                }
            }

            return list;
        }

        /// <summary>
        /// Translate the given expression to query texts
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public virtual IList<string> GetQueries(Expression expression)
        {
            this._queries.Clear();
            this.Visit(expression);

            for (int i = 0; i < this._queries.Count; i++)
            {
                this._queries[i] = this._queries[i] + this._queryString;
            }

            return this._queries.Distinct(this.GetQueryComparer()).ToList();
        }

        /// <summary>
        /// Create sub translator for recursion translation
        /// </summary>
        /// <returns></returns>
        protected abstract TranslatorBase CreateSubTranslator();

        /// <summary>
        /// Create the equality comparer for comparing the query
        /// </summary>
        /// <returns></returns>
        protected abstract IEqualityComparer<string> GetQueryComparer();

        /// <summary>
        /// iterate the expression tree to lookup the query method
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m.Method.DeclaringType == typeof(Queryable) ||this.IsQueryMethod(m.Method))
            {
                switch (m.Method.Name)
                {
                    case "Union":
                        {
                            var left = this.CreateSubTranslator();
                            var right = this.CreateSubTranslator();
                            this._queries = left.GetQueries(m.Arguments[0])
                                .Union(right.GetQueries(m.Arguments[1]), this.GetQueryComparer()).ToList();

                            return m;
                        }
                    case "Concat":
                        {
                            var left = this.CreateSubTranslator();
                            var right = this.CreateSubTranslator();
                            this._queries = left.GetQueries(m.Arguments[0])
                                .Concat(right.GetQueries(m.Arguments[1])).ToList();

                            return m;
                        }
                    case "Where":
                        {
                            if (m.Arguments[0].NodeType == ExpressionType.Constant)
                            {
                                // find the most innerest where
                                Type entityType = m.Arguments[0].Type.GetGenericArguments()[0];
                                var condition = ((LambdaExpression)
                                    ((UnaryExpression)m.Arguments[1]).Operand).Body;
                                this._queries = this.TranslateWhere(entityType, condition);

                                return m;
                            }

                            break;
                        }
                    case "OrderBy":
                    case "OrderByDescending":
                    case "Skip":
                    case "Take":
                    case "First":
                    case "Last":
                    case "Count":
                    case "Single":
                        {
                            this._queryString += this.TranslateQueryString(m);
                            break;
                        }
                    default:
                        {
                            var q = this.TranslateUnkownQueryMethod(m);

                            if (!string.IsNullOrEmpty(q))
                            {
                                this._queries.Add(q);
                            }

                            break;
                        }
                }
            }

            return base.VisitMethodCall(m);
        }

        protected abstract string TranslateUnkownQueryMethod(MethodCallExpression m);

        /// <summary>
        /// translates the most inner where clause to queries
        /// </summary>
        /// <param name="entityType"></param>
        /// <param name="condition"></param>
        /// <returns></returns>
        private IList<string> TranslateWhere(Type entityType, Expression condition)
        {
            if (condition.NodeType == ExpressionType.OrElse ||
                condition.NodeType == ExpressionType.AndAlso)
            {
                BinaryExpression expr = (BinaryExpression)condition;
                var left = this.TranslateWhere(entityType, expr.Left);
                var right = this.TranslateWhere(entityType, expr.Right);

                if (condition.NodeType == ExpressionType.OrElse)
                {
                    return this.CombineOrElseQuery(left, right);
                }
                else
                {
                    return this.CombineAndAlsoQuery(left, right);
                }
            }

            // to translate the inner method call expression or member acess expression
            // this could get only one query
            var query = this.TranslateQuery(entityType, condition);
            var list = new List<string>();

            if (!string.IsNullOrEmpty(query))
            {
                list.Add(query);
            }

            return list;
        }

        protected virtual IList<string> CombineAndAlsoQuery(IList<string> left, IList<string> right)
        {
            return TranslatorBase.Union(left, right, this.GetQueryComparer());
        }

        protected virtual IList<string> CombineOrElseQuery(IList<string> left, IList<string> right)
        {
            return left.Union(right, this.GetQueryComparer()).ToList(); 
        }

        protected abstract string TranslateQuery(Type entityType, Expression condition);

        protected abstract bool IsQueryMethod(MethodInfo methodInfo);

        protected abstract string TranslateQueryString(MethodCallExpression m);
    }
}
